/**
 * Created by chenyong on 2015/4/26.
 * Windows Azure 联合元数据
 */
'use strict';

var xml2js = require('xml2js');
var request = require('request');
var aadutils = require('./aadutils');
var async = require('async');

var Metadata = function (url) {
    if(!url) {
        throw new Error("Metadata: url is a required argument");
    }
    this.url = url;
    this.metadata = null;
};

Object.defineProperty(Metadata, 'url', {
    get: function () {
        return this.url;
    }
});

Object.defineProperty(Metadata, 'saml', {
    get: function () {
        return this.saml;
    }
});

Object.defineProperty(Metadata, 'wsfed', {
    get: function () {
        return this.wsfed;
    }
});

Object.defineProperty(Metadata, 'oauth', {
    get: function () {
        return this.oauth;
    }
});


Object.defineProperty(Metadata, 'metadata', {
    get: function () {
        return this.metadata;
    }
});

exports.getElement = function (parentElement, elementName) {
    if (parentElement['saml:' + elementName]) {
        return parentElement['saml:' + elementName];
    } else if (parentElement['samlp:'+elementName]) {
        return parentElement['samlp:'+elementName];
    }
    return parentElement[elementName];
};


exports.getFirstElement = function (parentElement, elementName) {
    var element = null;

    if (parentElement['saml:' + elementName]) {
        element =  parentElement['saml:' + elementName];
    } else if (parentElement['samlp:'+elementName]) {
        element =  parentElement['samlp:'+elementName];
    } else {
        element = parentElement[elementName];
    }
    return Array.isArray(element) ? element[0] : element;
};



Metadata.prototype.updateSamlMetadata = function(doc, next) {
    try {
        this.saml = {};

        var entity = aadutils.getElement(doc, 'EntityDescriptor');
        var idp = aadutils.getElement(entity, 'IDPSSODescriptor');
        var signOn = aadutils.getElement(idp[0], 'SingleSignOnService');
        var signOff = aadutils.getElement(idp[0], 'SingleLogoutService');
        var keyDescriptor = aadutils.getElement(idp[0], 'KeyDescriptor');
        this.saml.loginEndpoint = signOn[0].$.Location;
        this.saml.logoutEndpoint = signOff[0].$.Location;

        // copy the x509 certs from the metadata
        this.saml.certs = [];
        for (var j=0;j<keyDescriptor.length;j++) {
            this.saml.certs.push(keyDescriptor[j].KeyInfo[0].X509Data[0].X509Certificate[0]);
        }
        next(null);
    } catch (e) {
        next(new Error('Invalid SAMLP Federation Metadata ' + e.message));
    }
};

Metadata.prototype.updateWsfedMetadata = function(doc, next) {
    try {
        this.wsfed = {};
        var entity = aadutils.getElement(doc, 'EntityDescriptor');
        var roles = aadutils.getElement(entity, 'RoleDescriptor');
        for(var i = 0; i < roles.length; i++) {
            var role = roles[i];
            if(role['fed:SecurityTokenServiceEndpoint']) {
                var endpoint = role['fed:SecurityTokenServiceEndpoint'];
                var endPointReference = aadutils.getFirstElement(endpoint[0],'EndpointReference');
                this.wsfed.loginEndpoint = aadutils.getFirstElement(endPointReference,'Address');

                var keyDescriptor = aadutils.getElement(role, 'KeyDescriptor');
                // copy the x509 certs from the metadata
                this.wsfed.certs = [];
                for (var j=0;j<keyDescriptor.length;j++) {
                    this.wsfed.certs.push(keyDescriptor[j].KeyInfo[0].X509Data[0].X509Certificate[0]);
                }
                break;
            }
        }

        return next(null);
    } catch (e) {
        next(new Error('Invalid WSFED Federation Metadata ' + e.message));
    }
};

Metadata.prototype.updateOAuthMetadata = function(doc, next) {
    try {
        this.oauth = {};
        var entity = aadutils.getElement(doc, 'EntityDescriptor');
        var roles = aadutils.getElement(entity, 'RoleDescriptor');
        for(var i = 0; i < roles.length; i++) {
            var role = roles[i];
            if(role['fed:SecurityTokenServiceEndpoint']) {
                var endpoint = role['fed:SecurityTokenServiceEndpoint'];
                var endPointReference = aadutils.getFirstElement(endpoint[0],'EndpointReference');
                this.wsfed.loginEndpoint = aadutils.getFirstElement(endPointReference,'Address');

                var keyDescriptor = aadutils.getElement(role, 'KeyDescriptor');
                // copy the x509 certs from the metadata
                this.wsfed.certs = [];
                for (var j=0;j<keyDescriptor.length;j++) {
                    this.wsfed.certs.push(keyDescriptor[j].KeyInfo[0].X509Data[0].X509Certificate[0]);
                }
                break;
            }
        }

        return next(null);
    } catch (e) {
        next(new Error('Invalid OAuth Federation Metadata ' + e.message));
    }
};


Metadata.prototype.fetch = function(callback) {
    var self = this;

    async.waterfall([
        // fetch the Federation metadata for the AAD tenant
        function(next){
            request(self.url, function (err, response, body) {
                if(err) {
                    next(err);
                } else if(response.statusCode !== 200) {
                    next(new Error("Error:" + response.statusCode +  " Cannot get AAD Federation metadata from " + self.url));
                } else {
                    next(null, body);
                }
            });
        },
        function(body, next){
            // parse the AAD Federation metadata xml
            var parser = new xml2js.Parser({explicitRoot:true});
            // Note: xml responses from Azure AAD have a leading \ufeff which breaks xml2js parser!
            parser.parseString(body.replace("\ufeff", ""), function (err, data) {
                self.metatdata = data;
                next(err);

            });
        },
        function(next){
            // update the SAML SSO endpoints and certs from the metadata
            self.updateSamlMetadata(self.metatdata, next);
        },
        function(next){
            // update the SAML SSO endpoints and certs from the metadata
            self.updateWsfedMetadata(self.metatdata, next);
        },
        function(next){
            // update the SAML SSO endpoints and certs from the metadata
            self.updateOAuthMetadata(self.metatdata, next);
        }
    ], function (err) {
        // return err or success (err === null) to callback
        callback(err);
    });
};

exports.Metadata = Metadata;